package emas.impl;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import common.BasicAgent;
import common.GeneticOperations;

import emas.inf.*;


public class EmasAgent<Genotype> implements Agent<Genotype>, Parameters {
	private Island<Genotype> _homeIsland;
	private int _energy;
	protected GeneticOperations<Genotype> go;
	private Genotype _genotype;
	private List<Double> values;
	private List<Double> fitnesses;

	//informacje agenta
	List<Island<Genotype>> _availableIslands;	//wezly, do ktorych agent moze migrowac
	List<Agent<Genotype>> _neighbors;	//agenci na tej samej wyspie
	Species _agentSpecies;	//gatunek agenta
	Sex _agentSex;	//plec agenta
	int _reproductionThreshold; //prog reprodukcji
	int _dieThreshold;	//prog smierci
	int _startEnergy;	//poczatkowa energia
	int _basicEnergyQuantum;	//podstawowe kwantum energii

	//statystyki
	public static int umarlo = 0;
	public static int powstalo = 0;
	public static int spotkalo = 0;
	public static int stosunki = 0;
	public static int migracje = 0;
	public static int srodowisko = 0;

	public EmasAgent(Genotype aGenotype, GeneticOperations<Genotype> aGo, Island<Genotype> aI, List<Island<Genotype>> aAvailableIslands, Species aSpecies, Sex aSex, int aReproductionThreshold, int aDieThreshold, int aStartEnergy, int aBasicEnergyQuantum) {
		this._homeIsland = aI;
		this._genotype = aGenotype;
		this.go = aGo;


		this._availableIslands = aAvailableIslands;
		this._agentSpecies = aSpecies;
		this._agentSex = aSex;
		this._reproductionThreshold = aReproductionThreshold;
		this._dieThreshold = aDieThreshold;
		this._startEnergy = aStartEnergy;
		this._energy = aStartEnergy;
		this._basicEnergyQuantum = aBasicEnergyQuantum;

		//dodanie inf o sobie do wyspy i innych agentow
		this._homeIsland.addAgent(this);

		this.values = go.getValues(this._genotype);
		this.fitnesses = go.getFitnessesFromValue(this.values);
		
		this._neighbors = new ArrayList<Agent<Genotype>>();

		powstalo++;
	}

	public EmasAgent(EmasAgent<Genotype> agent) {
		this._homeIsland = agent._homeIsland;
		this._genotype = agent._genotype;
		this.go = agent.go;


		this._availableIslands = agent._availableIslands;
		this._agentSpecies = agent._agentSpecies;
		this._agentSex = agent._agentSex;
		this._reproductionThreshold = agent._reproductionThreshold;
		this._dieThreshold = agent._dieThreshold;
		this._startEnergy = agent._startEnergy;
		this._energy = agent._energy;
		this._basicEnergyQuantum = agent._basicEnergyQuantum;

		//dodanie inf o sobie do wyspy i innych agentow
		this._homeIsland.addAgent(this);

		this.values = go.getValues(this._genotype);
		this.fitnesses = go.getFitnessesFromValue(this.values);
		
		this._neighbors = new ArrayList<Agent<Genotype>>();

		powstalo++;
	}

	private void die() {
		//usuniecie sie ze swojej wyspy i innych agentow
		this._homeIsland.removeAgent(this);

		umarlo++;
	}

	//do innego agenta
	public void transfer(Agent<Genotype> aToAgent) {
		this._energy -= this._basicEnergyQuantum;
		aToAgent.assimilateEnergy();
	}

	private void transferToEnv(int aEnergy) {
		this._energy -= aEnergy;
		this._homeIsland.increaseEnergy(aEnergy);
	}

	/**
	 * Original one from PDF
	 * @param genes1
	 * @param genes2
	 * @return
	 */
	protected static double[][] crossover2(double[] genes1, double[] genes2) {
		double[][] result = new double[2][];
		result[0] = new double[genes1.length];
		result[1] = new double[genes1.length];

		for(int i = 0; i < genes1.length; i++) {
			double randVal = Math.random();
			double g1 = randVal * genes1[i] + (1 - randVal) * genes2[i];
			result[0][i] = g1;
			double g2 = randVal * genes2[i] + (1 - randVal) * genes1[i];
			result[1][i] = g2;
			//log.debug(String.format("gen 1 : %f gen 2 : %f, g1 : %f, g2 : %f",genes1[i],genes2[i],g1,g2));
		}

		return result;
	}

	/**
	 * SBX Crossover
	 * @param genes1
	 * @param genes2
	 * @return
	 */
	protected static double[][] crossover(double[] genes1, double[] genes2) {

		double p_c = 0.8; // crossover probability
		double eta_c = 15;

		double[][] result = new double[2][];
		result[0] = new double[genes1.length];
		result[1] = new double[genes1.length];

		for (int i = 0; i < genes1.length; i++) {
			// for every gene
			double c = Math.random();
			if (c < p_c) {          
				double bq, u = Math.random();
				if (u <= 0.5) {
					bq = Math.pow(2.0 * u, 1.0 / (eta_c + 1.0));
				} else {
					bq = Math.pow(1 / (2 * (1 - u)), 1.0 / (eta_c + 1.0));
				}
				double ch1v = 0.5 * (((1.0 + bq) * genes1[i]) + (1.0 - bq) * genes2[i]);
				double ch2v = 0.5 * (((1.0 - bq) * genes1[i]) + (1.0 + bq) * genes2[i]);

				ch1v = normalize(ch1v);
				ch2v = normalize(ch2v);
				result[0][i] = ch1v;
				result[1][i] = ch2v;
				//System.out.println(String.format("gen %.4f %.4f jest %.4f %.4f zmiany %.8f %.8f",genes1[i],genes2[i],result[0][i],result[1][i],Math.abs(genes1[i]-result[0][i]),Math.abs(genes2[i]-result[1][i])));
			}
			else {
				result[0][i] = genes1[i];
				result[1][i] = genes2[i];
			}
		}
		return result;
	}

	private static double normalize(double value) {
		if (value < 0) {
			return 0;
		}
		else if (value > 1) {
			return 1;
		}
		return value;
	}

	private void create(Agent<Genotype> aPartner) {
		Agent<Genotype> newAgent1 = null;
		Agent<Genotype> newAgent2 = null;

		List<Genotype> childGenotypes = go.crossover(this.getGenotype(), aPartner.getGenotype());
		if (childGenotypes.size() == 1) {
			childGenotypes.addAll(go.crossover(aPartner.getGenotype(), this.getGenotype()));
		}

		newAgent1 = new EmasAgent<Genotype>(childGenotypes.get(0), this.go, this._homeIsland, this._availableIslands, this._agentSpecies, this._agentSex, this._reproductionThreshold, this._dieThreshold, this._startEnergy, this._basicEnergyQuantum);
		newAgent1.mut();

		newAgent2 = new EmasAgent<Genotype>(childGenotypes.get(1), this.go, this._homeIsland, this._availableIslands, this._agentSpecies, this._agentSex, this._reproductionThreshold, this._dieThreshold, this._startEnergy, this._basicEnergyQuantum);
		newAgent2.mut();

		this.loseEnergy(this._startEnergy);
		aPartner.loseEnergy(this._startEnergy);
	}

	public void mut() {
		go.mutate(this._genotype);
		calculateValues();
	}

	private void calculateValues() {
		this.values = go.getValues(this._genotype);
		this.fitnesses = go.getFitnessesFromValue(this.values);
	}

	private void move(Island<Genotype> aTarget) {
		//System.out.println("Przed " + ((EmasIsland<Genotype>)aTarget).getAgentsOp().size());
		Agent<Genotype> agent = new EmasAgent<Genotype>(this);
		//
				
		this._homeIsland.removeAgent(this);
		EmasAgent.umarlo--;
		//System.out.println("Po " + ((EmasIsland<Genotype>)aTarget).getAgentsOp().size());
		
		
		((EmasAgent<Genotype>) agent)._homeIsland = aTarget;
		aTarget.addAgentMigr(agent);
		
		
		/*assert aTarget != this._homeIsland;
		System.out.println("!!!W MOVE: Liczebnosc docelowej wyspy przed przeniesieniem " + ((EmasIsland<Genotype>)aTarget)._agentsOp.size());
		Agent<Genotype> agentTmp = this;
		
		agentTmp.setHomeIsland(aTarget);
		aTarget.addAgent(agentTmp);
		System.out.println("!!!Po: " + ((EmasIsland<Genotype>)aTarget)._agentsOp.size());
		System.out.println("!!!Homeisland przed: " + ((EmasIsland<Genotype>)this._homeIsland)._agentsOp.size());
		this._homeIsland.removeAgent(this);
		System.out.println("!!!Homeisland po: " + ((EmasIsland<Genotype>)this._homeIsland)._agentsOp.size());
		System.out.println();
		//this._homeIsland = aTarget;*/
	}

	/**
	 * 1 - ten agent dominuje, 0 - wzajemnie niezdominowani, -1 - ten agent zdominowany
	 * @param aAgent
	 * @return
	 */
	public int checkDomination(BasicAgent<Genotype> aAgent) {
		double this_f1 = values.get(0);
		double this_f2 = values.get(1);
		double other_f1 = aAgent.getValues().get(0);
		double other_f2 = aAgent.getValues().get(1);

		if( (this_f1 <= other_f1) && (this_f2 < other_f2) || (this_f1 < other_f1) && (this_f2 <= other_f2))
			return 1;
		else if( (other_f1 <= this_f1) && (other_f2 < this_f2) || (other_f1 < this_f1) && (other_f2 <= this_f2))
			return -1;
		else
			return 0;
	}

	private Agent<Genotype> seekMeet() {
		Random rand = new Random();
		int PartnerNum = rand.nextInt(this._neighbors.size());
		Agent<Genotype> Partner = this._neighbors.get(PartnerNum);

		return Partner;
	}

	private Agent<Genotype> seekRepro() {
		Random rand = new Random();
		int PartnerNum = rand.nextInt(this._neighbors.size());
		Agent<Genotype> Partner = this._neighbors.get(PartnerNum);

		return Partner;
	}

	private Island<Genotype> seekMigr() {
		Random rand = new Random();
		int IslandNum = rand.nextInt(this._availableIslands.size());
		Island<Genotype> island = this._availableIslands.get(IslandNum);

		return island;
	}

	public boolean acceptMeet(Agent<Genotype> aAskingAgent) {
		//zawsze akceptuje spotkanie, jesli ma energie
		if(this._energy >= this._basicEnergyQuantum)
			return true;
		else
			return false;
	}

	public boolean acceptRepro(Agent<Genotype> aAskingAgent) {
		if(this._energy > this._reproductionThreshold)
			return true;
		else
			return false;
	}

	private void migr() {
		double wantMigr = Math.random();

		//System.out.println("Migracja z prawdopodobienstwem " + wantMigr);
		//prawdopodobienstwo migracji 1/30
		if(wantMigr < 0.001) {
			//jesli ma gdzie
			if(this._availableIslands.size() > 0)
			{
				Island<Genotype> island = this.seekMigr();
				if(this._energy > this._dieThreshold + this._basicEnergyQuantum) {
					this.transferToEnv(_basicEnergyQuantum);
					this.move(island);
					//System.out.println(this + " migracja na wyspe " + island);
					migracje++;
				}
			}
		}
	}

	private void meet() {
		double wantMeet = Math.random();

		//prawdopodobienstwo spotkania 0.9
		//spotkanie jesli ma z kim
		if(this.getNeighbors().size() > 0) {
			if(wantMeet < 0.9) {
				if(this._energy >= this._basicEnergyQuantum)
				{
					Agent<Genotype> Partner = this.seekMeet();
					if(Partner.acceptMeet(this)) {
						//spotkanie
						//System.out.println("agent: f1: " + this.getFunc().getf1() + ", f2: " + this.getFunc().getf2() + " partner: f1: " + Partner.getFunc().getf1() + " f2: " + Partner.getFunc().getf2());
						if(this.checkDomination(Partner) == 1) {
							Partner.transfer(this);
						} else if(this.checkDomination(Partner) == -1) {
							this.transfer(Partner);
						}
						//System.out.println(this + ":Spotkalem sie. Energia agenta " + this._energy + " partnera: " + Partner.getEnergy());
						spotkalo++;
					}
				}
			}
		}
	}

	private void repro() {
		double wantRepro = Math.random();

		//prawdopodobienstwo reprodukcji 0.8
		//jesli ma z kim
		if(this.getNeighbors().size() > 1) {
			if(wantRepro < 0.8) {
				if(this._energy > this._reproductionThreshold) {
					Agent<Genotype> Partner = this.seekRepro();

					//System.out.println("reprodukcja - decyzja. Energia agenta " + this._energy + " energia partnera " + Partner.getEnergy());

					if(Partner.acceptRepro(this)) {
						this.create(Partner);
						//System.out.println(this + ": reprodukcja energia " + this._energy + " energia partnera " + Partner.getEnergy());
						stosunki++;
					}
				}
			}
		}
	}

	public void LifeStep() {
		if(this._energy <= this._dieThreshold) {
			this.transferToEnv(this._energy);
			this.die();
		} else {
			this.migr();
			this.meet();
			this.repro();
		}
	}

	public void assimilateEnergy() {
		this._energy += this._basicEnergyQuantum;
	}

	public void loseEnergy(int aAmont) {
		this._energy -= aAmont;
	}

	public void addNeighbors(List<Agent<Genotype>> aAgents) {
		this._neighbors = aAgents;
	}

	public void addNeighbor(Agent<Genotype> aAgent) {
		this._neighbors.add(aAgent);
	}

	public List<Agent<Genotype>> getNeighbors() {
		return this._neighbors;
	}

	public void removeNeigbor(Agent<Genotype> agent) {
		this._neighbors.remove(agent);
	}

	public int getEnergy() {
		return this._energy;
	}

	public Genotype getGenotype() {
		return this._genotype;	
	}

	public List<Double> getValues() {
		return this.values;
	}

	@Override
	public double getFitness(int index) {
		return this.fitnesses.get(index);
	}

	@Override
	public List<Double> getFitnesses() {
		return this.fitnesses;
	}

	@Override
	public double getValue(int index) {
		return this.values.get(index);
	}

	
	/*@Override
	public Island<Genotype> getHomeIsland() {
		// TODO Auto-generated method stub
		return null;
	}*/

	@Override
	public void setHomeIsland(Island<Genotype> island) {
		this._homeIsland = island;
		
	}

	/*private static <T extends BasicAgent<?>> double calculateHV(List<T> agents, double ref_f0, double ref_f1) {

		return GeneticHelper.calculateHiperValume(agents,ref_f0,ref_f1);
	}*/
}